package com.finupgroup.postloan.generator.config.builder;

import com.finupgroup.postloan.generator.config.*;
import com.finupgroup.postloan.generator.config.po.TableField;
import com.finupgroup.postloan.generator.config.po.TableInfo;
import com.finupgroup.postloan.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 配置汇总 传递给文件生成工具
 *
 * @author YueQingwei
 * @since 2018-04-10
 */
public class ConfigBuilder {

    /**
     * 数据库配置
     */
    private final DataSourceConfig dataSourceConfig;
    /**
     * SQL连接
     */
    private Connection connection;
    /**
     * SQL语句类型
     */
    private IDbQuery dbQuery;

    private String superEntityClass = ConstVal.SUPER_ENTITY_CLASS;
    private String superMapperClass = ConstVal.SUPERD_MAPPER_CLASS;
    private String superServiceClass = ConstVal.SUPERD_SERVICE_CLASS;
    private String superServiceImplClass = ConstVal.SUPERD_SERVICEIMPL_CLASS;
    private String superControllerClass;
    /**
     * 数据库表信息
     */
    private List<TableInfo> tableInfoList;

    /**
     * 模板信息
     */
    private TemplateInfo templateInfo;
    /**
     * 包配置详情
     */
    private PackageInfo packageInfo;
    /**
     * 路径配置信息
     */
    private Map<String, String> pathInfo;
    /**
     * 策略配置
     */
    private StrategyConfig strategyConfig;
    /**
     * 全局配置信息
     */
    private GlobalConfig globalConfig;

    /**
     * <p>
     * 在构造器中处理配置
     * </p>
     */
    public ConfigBuilder() {
        // 全局配置
        this.globalConfig = new GlobalConfig();
        this.templateInfo = new TemplateInfo();
        // 包配置
        this.packageInfo = new PackageInfo();
        handlePackage(this.globalConfig.getOutputDir());
        // 数据源配置
        this.dataSourceConfig = new DataSourceConfig();
        handleDataSource();
        // 策略配置
        this.strategyConfig = new StrategyConfig();
        handleStrategy(this.strategyConfig);

    }

    // ************************ 曝露方法 BEGIN*****************************

    /**
     * <p>
     * 所有路径配置
     * </p>
     *
     * @return 路径配置
     */
    public Map<String, String> getPathInfo() {
        return pathInfo;
    }


    public String getSuperEntityClass() {
        return superEntityClass;
    }


    public String getSuperMapperClass() {
        return superMapperClass;
    }

    /**
     * <p>
     * 获取超类定义
     * </p>
     *
     * @return 完整超类名称
     */
    public String getSuperServiceClass() {
        return superServiceClass;
    }


    public String getSuperServiceImplClass() {
        return superServiceImplClass;
    }


    public String getSuperControllerClass() {
        return superControllerClass;
    }


    /**
     * <p>
     * 表信息
     * </p>
     *
     * @return 所有表信息
     */
    public List<TableInfo> getTableInfoList() {
        return tableInfoList;
    }

    public ConfigBuilder setTableInfoList(List<TableInfo> tableInfoList) {
        this.tableInfoList = tableInfoList;
        return this;
    }

    // ****************************** 曝露方法 END**********************************

    /**
     * <p>
     * 处理包配置
     * </p>
     * @param outputDir
     */
    private void handlePackage(String outputDir) {
        // 生成路径信息
        pathInfo = new HashMap<>(6);
        pathInfo.put(ConstVal.ENTITY_PATH, joinPath(outputDir, packageInfo.getEntityProject(), packageInfo.getEntity()));
        pathInfo.put(ConstVal.MAPPER_PATH, joinPath(outputDir, packageInfo.getMapperProject(), packageInfo.getMapper()));
        pathInfo.put(ConstVal.XML_PATH, joinXmlPath(outputDir, packageInfo.getMapperProject(), packageInfo.getXml()));
        pathInfo.put(ConstVal.SERIVCE_PATH, joinPath(outputDir, packageInfo.getServiceProject(), packageInfo.getService()));
        pathInfo.put(ConstVal.SERVICEIMPL_PATH, joinPath(outputDir, packageInfo.getServiceProject(), packageInfo.getServiceImpl()));
        pathInfo.put(ConstVal.CONTROLLER_PATH, joinPath(outputDir, packageInfo.getControllerProject(), packageInfo.getController()));
    }


    /**
     * <p>
     * 处理数据源配置
     * </p>
     */
    private void handleDataSource() {
        connection = dataSourceConfig.getConn();
        dbQuery = dataSourceConfig.getDbQuery();
    }


    /**
     * <p>
     * 处理数据库表 加载数据库表、列、注释相关数据集
     * </p>
     *
     * @param config StrategyConfig
     */
    private void handleStrategy(StrategyConfig config) {
        tableInfoList = getTablesInfo(config);
    }

    /**
     * <p>
     * 处理表对应的类名称
     * </P>
     *
     * @param tableList 表名称
     * @param strategy  命名策略
     * @param config    策略配置项
     * @return 补充完整信息后的表
     */
    private static List<TableInfo> processTable(List<TableInfo> tableList, NamingStrategy strategy, StrategyConfig config) {
        String[] tablePrefix = config.getTablePrefix();
        String[] fieldPrefix = config.getFieldPrefix();
        for (TableInfo tableInfo : tableList) {
            tableInfo.setEntityName(NamingStrategy.capitalFirst(processName(tableInfo.getName(), strategy, tablePrefix)));
            tableInfo.setMapperName(tableInfo.getEntityName() + ConstVal.MAPPER);
            tableInfo.setXmlName(tableInfo.getEntityName() + ConstVal.MAPPER);
            tableInfo.setServiceName(tableInfo.getEntityName() + ConstVal.SERIVCE);
            tableInfo.setServiceImplName(tableInfo.getEntityName() + ConstVal.SERVICEIMPL);
            tableInfo.setControllerName(tableInfo.getEntityName() + ConstVal.CONTROLLER);
            //强制开启字段注解
            checkTableIdTableFieldAnnotation(tableInfo, fieldPrefix);
        }
        return tableList;
    }


    /**
     * <p>
     * 检查是否有
     * {@link com.baomidou.mybatisplus.annotations.TableId}
     * {@link com.baomidou.mybatisplus.annotations.TableField}
     * 注解
     * </p>
     *  @param tableInfo
     * @param fieldPrefix
     */
    private static void checkTableIdTableFieldAnnotation(TableInfo tableInfo, String[] fieldPrefix) {
        boolean importTableFieldAnnotation = false;
        boolean importTableIdAnnotation = false;
        if (fieldPrefix != null && fieldPrefix.length != 0) {
            for (TableField tf : tableInfo.getFields()) {
                if (NamingStrategy.isPrefixContained(tf.getName(), fieldPrefix)) {
                    if (tf.isKeyFlag()) {
                        importTableIdAnnotation = true;
                    }
                    importTableFieldAnnotation = true;
                }
            }
        }
        if (importTableFieldAnnotation) {
            tableInfo.getImportPackages().add(com.baomidou.mybatisplus.annotations.TableField.class.getCanonicalName());
        }
        if (importTableIdAnnotation) {
            tableInfo.getImportPackages().add(com.baomidou.mybatisplus.annotations.TableId.class.getCanonicalName());
        }
    }


    /**
     * <p>
     * 获取所有的数据库表信息
     * </p>
     */
    private List<TableInfo> getTablesInfo(StrategyConfig config) {
        boolean isInclude = (null != config.getInclude() && config.getInclude().length > 0);
        boolean isExclude = (null != config.getExclude() && config.getExclude().length > 0);
        if (isInclude && isExclude) {
            throw new RuntimeException("<strategy> 标签中 <include> 与 <exclude> 只能配置一项！");
        }
        //所有的表信息
        List<TableInfo> tableList = new ArrayList<>();

        //需要反向生成或排除的表信息
        List<TableInfo> includeTableList = new ArrayList<>();
        List<TableInfo> excludeTableList = new ArrayList<>();

        //不存在的表名
        Set<String> notExistTables = new HashSet<>();
        PreparedStatement preparedStatement = null;
        try {
            String tablesSql = dbQuery.tablesSql();
            preparedStatement = connection.prepareStatement(tablesSql);
            ResultSet results = preparedStatement.executeQuery();
            TableInfo tableInfo;
            while (results.next()) {
                String tableName = results.getString(dbQuery.tableName());
                if (StringUtils.isNotEmpty(tableName)) {
                    String tableComment = results.getString(dbQuery.tableComment());
                    if (config.isSkipView() && "VIEW".equals(tableComment)) {
                        // 跳过视图
                        continue;
                    }
                    tableInfo = new TableInfo();
                    tableInfo.setName(tableName);
                    tableInfo.setComment(tableComment);
                    if (isInclude) {
                        for (String includeTab : config.getInclude()) {
                            if (includeTab.equalsIgnoreCase(tableName)) {
                                includeTableList.add(tableInfo);
                            } else {
                                notExistTables.add(includeTab);
                            }
                        }
                    } else if (isExclude) {
                        for (String excludeTab : config.getExclude()) {
                            if (excludeTab.equalsIgnoreCase(tableName)) {
                                excludeTableList.add(tableInfo);
                            } else {
                                notExistTables.add(excludeTab);
                            }
                        }
                    }
                    tableList.add(tableInfo);
                } else {
                    System.err.println("当前数据库为空！！！");
                }
            }
            // 将已经存在的表移除，获取配置中数据库不存在的表
            for (TableInfo tabInfo : tableList) {
                notExistTables.remove(tabInfo.getName());
            }

            if (!notExistTables.isEmpty()) {
                System.err.println("表 " + notExistTables + " 在数据库中不存在！！！");
            }

            // 需要反向生成的表信息
            if (isExclude) {
                tableList.removeAll(excludeTableList);
                includeTableList = tableList;
            }
            if (!isInclude && !isExclude) {
                includeTableList = tableList;
            }
            /**
             * 性能优化，只处理需执行表字段 github issues/219
             */
            for (TableInfo ti : includeTableList) {
                this.convertTableFields(ti, config.getColumnNaming());
            }
        } catch (SQLException e) {
            System.err.println("SQL异常：" + e.getMessage());
        } finally {
            // 释放资源
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                System.err.println("SQL异常：" + e.getMessage());
            }
        }
        return processTable(includeTableList, config.getNaming(), config);
    }


    /**
     * <p>
     * 将字段信息与表信息关联
     * </p>
     *
     * @param tableInfo 表信息
     * @param strategy  命名策略
     * @return
     */
    private TableInfo convertTableFields(TableInfo tableInfo, NamingStrategy strategy) {
        boolean haveId = false;
        List<TableField> fieldList = new ArrayList<>();
        List<TableField> commonFieldList = new ArrayList<>();
        try {
            String tableFieldsSql = String.format(dbQuery.tableFieldsSql(), tableInfo.getName());
            PreparedStatement preparedStatement = connection.prepareStatement(tableFieldsSql);
            ResultSet results = preparedStatement.executeQuery();
            while (results.next()) {
                TableField field = new TableField();
                String key = results.getString(dbQuery.fieldKey());
                // 避免多重主键设置，目前只取第一个找到ID，并放到list中的索引为0的位置
                boolean isId = StringUtils.isNotEmpty(key) && key.equalsIgnoreCase("PRI");
                // 处理ID
                if (isId && !haveId) {
                    field.setKeyFlag(true);
                    if (dbQuery.isKeyIdentity(results)) {
                        field.setKeyIdentityFlag(true);
                    }
                    haveId = true;
                } else {
                    field.setKeyFlag(false);
                }
                // 自定义字段查询
                String[] fcs = dbQuery.fieldCustom();
                if (null != fcs) {
                    Map<String, Object> customMap = new HashMap<>();
                    for (String fc : fcs) {
                        customMap.put(fc, results.getObject(fc));
                    }
                    field.setCustomMap(customMap);
                }
                // 处理其它信息
                field.setName(results.getString(dbQuery.fieldName()));
                field.setType(results.getString(dbQuery.fieldType()));
                field.setPropertyName(processName(field.getName(), strategy));
                field.setColumnType(dataSourceConfig.getTypeConvert().processTypeConvert(field.getType()));
                field.setComment(results.getString(dbQuery.fieldComment()));
                if (strategyConfig.includeSuperEntityColumns(field.getName())) {
                    // 跳过公共字段
                    commonFieldList.add(field);
                    continue;
                }
                fieldList.add(field);
            }
        } catch (SQLException e) {
            System.err.println("SQL Exception：" + e.getMessage());
        }
        tableInfo.setFields(fieldList);
        tableInfo.setCommonFields(commonFieldList);
        return tableInfo;
    }


    private static String joinPath(String baseDir, String projectName, String packageName) {
        if (StringUtils.isEmpty(baseDir)) {
            baseDir = System.getProperty(ConstVal.JAVA_TMPDIR);
        }
        StringBuilder pathBuilder = new StringBuilder(baseDir);
        if (!StringUtils.endsWith(baseDir, File.separator)) {
            pathBuilder.append(File.separator);
        }
        if (StringUtils.isNotEmpty(projectName)) {
            pathBuilder.append(projectName).append(File.separator)
                    .append("src").append(File.separator)
                    .append("main").append(File.separator)
                    .append("java").append(File.separator);
        }
        packageName = packageName.replaceAll("\\.", "\\" + File.separator);
        pathBuilder.append(packageName);
        return pathBuilder.toString();
    }

    private static String joinXmlPath(String baseDir, String projectName, String packageName) {
        if (StringUtils.isEmpty(baseDir)) {
            baseDir = System.getProperty(ConstVal.JAVA_TMPDIR);
        }
        StringBuilder pathBuilder = new StringBuilder(baseDir);
        if (!StringUtils.endsWith(baseDir, File.separator)) {
            pathBuilder.append(File.separator);
        }
        if (StringUtils.isNotEmpty(projectName)) {
            pathBuilder.append(projectName).append(File.separator)
                    .append("src").append(File.separator)
                    .append("main").append(File.separator)
                    .append("resources").append(File.separator);
        }
        pathBuilder.append(packageName);
        return pathBuilder.toString();
    }

    /**
     * <p>
     * 处理字段名称
     * </p>
     *
     * @return 根据策略返回处理后的名称
     */
    private String processName(String name, NamingStrategy strategy) {
        return processName(name, strategy, this.strategyConfig.getFieldPrefix());
    }


    /**
     * <p>
     * 处理表/字段名称
     * </p>
     *
     * @param name
     * @param strategy
     * @param prefix
     * @return 根据策略返回处理后的名称
     */
    private static String processName(String name, NamingStrategy strategy, String[] prefix) {
        boolean removePrefix = false;
        if (prefix != null && prefix.length >= 1) {
            removePrefix = true;
        }
        String propertyName;
        if (removePrefix) {
            if (strategy == NamingStrategy.underline_to_camel) {
                // 删除前缀、下划线转驼峰
                propertyName = NamingStrategy.removePrefixAndCamel(name, prefix);
            } else {
                // 删除前缀
                propertyName = NamingStrategy.removePrefix(name, prefix);
            }
        } else if (strategy == NamingStrategy.underline_to_camel) {
            // 下划线转驼峰
            propertyName = NamingStrategy.underlineToCamel(name);
        } else {
            // 不处理
            propertyName = name;
        }
        return propertyName;
    }


    public StrategyConfig getStrategyConfig() {
        return strategyConfig;
    }


    public ConfigBuilder setStrategyConfig(StrategyConfig strategyConfig) {
        this.strategyConfig = strategyConfig;
        return this;
    }


    public GlobalConfig getGlobalConfig() {
        return globalConfig;
    }


    public ConfigBuilder setGlobalConfig(GlobalConfig globalConfig) {
        this.globalConfig = globalConfig;
        return this;
    }

    public PackageInfo getPackageInfo() {
        return packageInfo;
    }

    public TemplateInfo getTemplateInfo() {
        return templateInfo;
    }
}
